/**
  opendap-data-source.c - Created by Timothy Morey on 7/18/1012
*/

#include <netcdf.h>
#include <string.h>

#include "data-source.h"
#include "err.h"
#include "logger.h"
#include "netcdf-utils.h"
#include "thread-utils.h"

#ifdef __WINDOWS__
#pragma warning(disable:4996)
#endif

/*****************************************************************************
 * Local definitions
 *****************************************************************************/

typedef struct
{
  DataSource Base;
  Thread ReaderThread;
  int ReaderThreadAlive;
} OPeNDAPDataSource;

#ifdef __WINDOWS__
DWORD WINAPI OPeNDAPReaderThreadEntryPoint(LPVOID arg);
#else
void* OPeNDAPReaderThreadEntryPoint(void* arg);
#endif


/*****************************************************************************
 * Implementation of data-source.h functions
 *****************************************************************************/

int OPeNDAPDataSourceDisconnect(int dsid)
{
  int retval = NVN_NOERR;
  
  // TODO...

  return retval;
}

int OPeNDAPDataSourceGetDatasetSchema(DataSourceDescriptor desc,
                                      DataSetSchema* schema)
{
  int retval = NVN_NOERR;

  BuildDataSetSchema(desc.Dataset, schema);

  return retval;
}

int OPeNDAPDataSourceGetGridSchema(DataSourceDescriptor desc,
                                   DataGridSchema* schema)
{
  int retval = NVN_NOERR;

  BuildDataGridSchema(desc.Dataset, desc.Varname, schema);

  return retval;
}

int OPeNDAPDataSourceStartStream(DataSourceDescriptor desc,
                                 DataFrameCallback callback,
                                 int* dsid)
{
  int retval = NVN_NOERR;
  int i = 0;
  int pos = -1;
  OPeNDAPDataSource* ds = 0;

  if(! dsid)
  {
    retval = NVN_EINVARGS;
  }

  if(NVN_NOERR == retval)
  {
    ds = (OPeNDAPDataSource*)malloc(sizeof(OPeNDAPDataSource));
    retval = RegisterDataSource((DataSource*)ds, dsid);
  }

  if(NVN_NOERR == retval)
  {
    memset(ds, 0, sizeof(OPeNDAPDataSource));
    ds->Base.Descriptor = desc;
    ds->Base.Type = DataSourceTypeOPeNDAP;
    ds->Base.Callback = callback;
  }

  if(NVN_NOERR == retval)
  {
#ifdef __WINDOWS__

    ds->ReaderThread.Handle = 
      CreateThread(0, 0, OPeNDAPReaderThreadEntryPoint, ds, 0, &ds->ReaderThread.Id);

    if(0 >= ds->ReaderThread.Handle)
#else

    if(0 != pthread_create(&ds->ReaderThread.Handle, 0, OPeNDAPReaderThreadEntryPoint, ds))
#endif
    {
      WriteMessage(LogLevelError, "Failed to launch server thread");
      retval = NVN_ETHREADFAIL;
    }
  }
  
  if(NVN_NOERR == retval)
  {
    ds->Base.ID = *dsid;
  }
  else
  {
    if(ds)
    {
      free(ds);
      UnregisterDataSource(*dsid);
    }
  }

  return retval;
}


/*****************************************************************************
 * Implementation of local functions
 *****************************************************************************/

#ifdef __WINDOWS__
DWORD WINAPI OPeNDAPReaderThreadEntryPoint(LPVOID arg)
#else
void* OPeNDAPReaderThreadEntryPoint(void* arg)
#endif
{
  int retval = NVN_NOERR;

  OPeNDAPDataSource* ds = (OPeNDAPDataSource*)arg;
  int ncstatus = NC_NOERR;
  int ncid = 0;
  int varid = 0;
  nc_type vartype;
  int ndims = 0;
  int dimids[NC_MAX_VAR_DIMS];
  int unlimdimid = -1;
  int i = 0;
  int xdimpos = -1, ydimpos = -1, tdimpos = -1;
  size_t xdimlen = 0, ydimlen = 0, tdimlen = 0;
  char* buf = 0;
  int buflen;
  char logmsg[256];

  if(! ds)
  {
    retval = NVN_EINVARGS;
  }    

  if(NVN_NOERR == retval &&
    NC_NOERR != (ncstatus = nc_open(ds->Base.Descriptor.Dataset, 0, &ncid)))
  {
    sprintf(logmsg, "failed to nc_open %s with code %d", ds->Base.Descriptor.Dataset, ncstatus);
    WriteMessage(LogLevelError, logmsg);
    retval = NVN_ENETCDFFAIL;
  }

  if(NVN_NOERR == retval &&
    NC_NOERR != (ncstatus = nc_inq_varid(ncid, ds->Base.Descriptor.Varname, &varid)))
  {
    sprintf(logmsg, "nc_inq_varid failed with %d", ncstatus);
    WriteMessage(LogLevelError, logmsg);
    retval = NVN_ENETCDFFAIL;
  }

  if(NVN_NOERR == retval &&
    NC_NOERR != (ncstatus = nc_inq_var(ncid, varid, 0, &vartype, &ndims, dimids, 0)))
  {
    sprintf(logmsg, "nc_inq_var failed with %d", ncstatus);
    WriteMessage(LogLevelError, logmsg);
    retval = NVN_ENETCDFFAIL;
  }

  if(NVN_NOERR == retval &&
    NC_NOERR != (ncstatus = nc_inq_unlimdim(ncid, &unlimdimid)))
  {
    sprintf(logmsg, "nc_inq_unlimdim failed with %d", ncstatus);
    WriteMessage(LogLevelError, logmsg);
    retval = NVN_ENETCDFFAIL;
  }  
  
  if(NVN_NOERR == retval)
  {
    for(i = 0; i < ndims; i++)
    {
      if(dimids[i] == unlimdimid)
      {
        tdimpos = i;
      }
      else if(xdimpos == -1)
      {
        xdimpos = i;
      }
      else
      {
        ydimpos = i;
      }
    }

    if(xdimpos == -1 || ydimpos == -1 || tdimpos == -1)
    {
      WriteMessage(LogLevelError, "failed to identify variable dimensions");
      retval = NVN_ERROR;
    }
  }

  if(NVN_NOERR == retval &&
    (NC_NOERR != (ncstatus = nc_inq_dimlen(ncid, dimids[xdimpos], &xdimlen)) ||
     NC_NOERR != (ncstatus = nc_inq_dimlen(ncid, dimids[ydimpos], &ydimlen)) ||
     NC_NOERR != (ncstatus = nc_inq_dimlen(ncid, dimids[tdimpos], &tdimlen))))
  {
    sprintf(logmsg, "nc_inq_dimlen failed with %d", ncstatus);
    WriteMessage(LogLevelError, logmsg);
    retval = NVN_ENETCDFFAIL;
  }

  if(NVN_NOERR == retval && 
     xdimlen > 0 && ydimlen > 0 && tdimlen > 0)
  {
    GetTypeSize(vartype, &buflen);

    if(buflen <= 0)
    {
      sprintf(logmsg, "unrecognized variable type: %d", vartype);
      WriteMessage(LogLevelError, logmsg);
      retval = NVN_ENETCDFFAIL;
    }

    buflen *= xdimlen;
    buflen *= ydimlen;
    buf = (char*)malloc(buflen);
  }

  if(NVN_NOERR == retval && buf && buflen > 0)
  {
    size_t startslab[NC_MAX_DIMS];
    size_t countslab[NC_MAX_DIMS];

    countslab[xdimpos] = xdimlen;
    countslab[ydimpos] = ydimlen;
    countslab[tdimpos] = 1;

    for(i = 0; NVN_NOERR == retval && (size_t)i < tdimlen; i++)
    {
      startslab[xdimpos] = 0;
      startslab[ydimpos] = 0;
      startslab[tdimpos] = i;

      if(NC_NOERR == (ncstatus = nc_get_vara(ncid, varid, startslab, countslab, buf)))
      {
        ds->Base.Callback(ds->Base.ID, buf, buflen);
      }
      else
      {
        sprintf(logmsg, "nc_get_vara failed with %d", ncstatus);
        WriteMessage(LogLevelError, logmsg);
        retval = NVN_ENETCDFFAIL;
      }
    }
  }

  return retval;
}
